package strategy

import (
	"container/list"
)

const Int64Max = int64(^uint64(0) >> 1)

// LFUCache unsafe
type LFUCache struct {
	maxBytes  int64
	nBytes    int64
	ll        *list.List
	cache     map[string]*list.Element
	OnEvicted func(key string, value Value)
}

func NewLFUCache(maxBytes int64, onEvicted func(string, Value)) *LFUCache {
	return &LFUCache{
		maxBytes:  maxBytes,
		ll:        list.New(),
		cache:     make(map[string]*list.Element),
		OnEvicted: onEvicted,
	}
}

func (L *LFUCache) Get(key string) (value Value, ok bool) {
	if ele, ok := L.cache[key]; ok {
		value = ele.Value.(*entry).value
		L.update(ele)
		return value, ok
	}

	return nil, false
}

func (L *LFUCache) Add(key string, value Value) {
	var delta int64
	if ele, ok := L.cache[key]; ok {
		delta = int64(value.Len() - ele.Value.(*entry).value.Len())
	} else {
		delta = int64(len(key) + value.Len())
	}

	for L.maxBytes != 0 && L.nBytes+delta > L.maxBytes && L.nBytes != 0 {
		L.RemoveLess()
	}

	//
	ele := L.ll.PushBack(&entry{key: key, value: value})
	L.cache[key] = ele
	L.nBytes += delta

}

func (L *LFUCache) Len() int {
	return L.ll.Len()
}

func (L *LFUCache) RemoveLess() {
	i := L.ll.Front()
	if i == nil {
		return
	}

	ele := &list.Element{Value: &entry{count: Int64Max}}

	for ; i != nil; i = i.Next() {
		if i.Value.(*entry).count < ele.Value.(*entry).count {
			ele = i
		}
	}

	L.ll.Remove(ele)
	e := ele.Value.(*entry)
	delete(L.cache, e.key)
	L.nBytes -= int64(len(e.key) + e.value.Len())
	if L.OnEvicted != nil {
		L.OnEvicted(e.key, e.value)
	}
}

func (L *LFUCache) update(ele *list.Element) {
	ele.Value.(*entry).count++
}

var _ Strategy = (*LFUCache)(nil)
